﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Cubicle.Core.Shared;
using Cubicle.Nuget.Config;
using NuGet;

namespace Cubicle.Nuget.Core
{
    public class NugetClient
    {

        private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

        public readonly IPackageRepository Repository;
        public readonly PackageManager Manager;
        public readonly string Name;
        public bool IsDefault;
        public readonly string Uri;
        public readonly bool IsLocal;

        public NugetClient(string name, bool isDefault, string uri)
        {
            Name = name;
            IsDefault = isDefault;
            Uri = uri;
            IsLocal = Directory.Exists(uri);
            Repository = PackageRepositoryFactory.Default.CreateRepository(uri);
            Manager = new PackageManager(
                         Repository,
                         new DefaultPackagePathResolver(uri),
                         new PhysicalFileSystem(InstallPathFinder.Singleton.PackagePath)
                       );
            Manager.Logger = new NugetNLogger();

            Manager.PackageInstalled += (sender, e) => InstallToBin(e.Package);
            Manager.PackageUninstalling += (sender, e) => UninstallFromBin(e.Package);
        }

        public bool ReinstallToBin()
        {
            var success = true;
            foreach (var package in GetInstalled())
            {
                success &= UninstallFromBin(package);
                success &= InstallToBin(package);
            }
            return success;
        }

        private bool UninstallFromBin(IPackage package)
        {
            try
            {
                _logger.Info("Removing \bin files. Package = {0}", package.Id);
                return package.UninstallFromBin();
            }
            catch (Exception ex)
            {
                _logger.ErrorException("Error copying files to \bin.", ex);
                return false;
            }
        }

        private bool InstallToBin(IPackage package)
        {
            try
            {
                _logger.Info("Installing \bin files. Package = {0}", package.Id);
                return package.InstallToBin();
            }
            catch (Exception ex)
            {
                _logger.ErrorException("Error copying files to \bin.", ex);
                return false;
            }
        }

        public IEnumerable<IPackage> this[string id, bool allowPrereleaseVersions = false]
        {
            get { return Find(id, allowPrereleaseVersions); }
        }

        public IPackage this[string id, string version, bool allowPrereleaseVersions = false]
        {
            get { return Find(id, version, allowPrereleaseVersions); }
        }

        public IQueryable<IPackage> Search(string searchTerm, bool allowPrereleaseVersions = false)
        {
            return Repository.Search(searchTerm, allowPrereleaseVersions);
        }

        public IEnumerable<IPackage> Find(string id, bool allowPrereleaseVersions = false)
        {
            var versionSpec = new VersionSpec();
            return Repository.FindPackages(id, versionSpec, allowPrereleaseVersions, false);
        }

        public IPackage Find(string id, string version, bool allowPrereleaseVersions = false)
        {
            SemanticVersion semanticVersion;
            return SemanticVersion.TryParse(version, out semanticVersion) 
                ? Repository.FindPackage(id, semanticVersion, allowPrereleaseVersions, false) 
                : null;
        }

        public IQueryable<IPackage> GetPackages()
        {
            return Repository.GetPackages();
        }

        public IQueryable<IPackage> GetInstalled()
        {
            return Manager.LocalRepository.GetPackages();
        }

        public IEnumerable<IPackage> GetUpdates()
        {
            return IsLocal 
                ? new List<IPackage>() 
                : Repository.GetUpdates(Manager.LocalRepository.GetPackages(), false, false);
        }

        public bool InstallPackage(string id, string version = null, bool acceptLicense = false)
        {
            if (Manager.IsInstalled(id, version))
            {
                _logger.Error(Helpers.Format("Package already installed. id = {0}, version = {1}", id, version));
                return false;
            }

            IPackage versionMatched;
            if (!Repository.Match(id, version, out versionMatched))
            {
                _logger.Error(Helpers.Format("Package not found in repository: id = {0}, version = {1}", id, version));
                return false;
            }
            
            if (versionMatched.RequireLicenseAcceptance && !acceptLicense)
            {
                _logger.Error("Package installation requires license acceptance".Format());
                return false;
            }
            
            try
            {
                Manager.InstallPackage(versionMatched, false, false);
                return true;
            }
            catch (Exception e)
            {
                _logger.ErrorException("Package installation error.", e);
                return false;
            }
        }

        public bool UninstallPackage(string id, string version = null, bool removeDependencies = false)
        {
            IPackage package;
            if (!Manager.LocalRepository.Match(id, version, out package))
            {
                _logger.Error(Helpers.Format("Package not installed. id = {0}, version = {1}", id, version));
                return false;
            }

            try
            {
                // Unistall package
                Manager.UninstallPackage(package, false, removeDependencies);
                return true;
            } 
            catch (Exception e)
            {
                _logger.ErrorException("Package uninstallation error.", e);
                return false;
            }
        }

        public bool UpdatePackage(string id, bool updateDependencies = true)
        {
            if (!Manager.IsInstalled(id))
            {
                _logger.Error(Helpers.Format("Package not installed. id = {0}", id));
                return false;
            }

            Manager.UpdatePackage(id, updateDependencies, false);
            return true;
        }

        public bool UpdatePackage(IPackage package, bool updateDependencies = true)
        {
            var updates = Manager.SourceRepository.GetUpdates(new List<IPackage> {package} , false, true);
            if (!updates.Any()) return false;
            Manager.UpdatePackage(updates.FirstOrDefault(), updateDependencies, false);
            return true;
        }

    }
}
